package com.haojiangbo.config.mapping;

import com.haojiangbo.config.properties.ServiceMagicProperties;
import com.haojiangbo.config.result.ServiceMagicResultHander;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *  这个配置，会扫描所有标记有 @service
 *  的bean 并且把他们 以 /Call-ServiceName-methodName
 *  的方式 注册到 MATCH 中
 * 　@author 郝江波
 * 　@date 2021/2/8 9:57
 *
 */
public class RequestMappingConfig implements HandlerMapping, ApplicationContextAware, InitializingBean, Ordered {

    @Autowired
    ServiceMagicProperties serviceMagicProperties;

    private static ApplicationContext applicationContext;
    private static final Map<String, HandlerMethod> MATCH = new LinkedHashMap<>();

    public void initFunction() {
        String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class);
        for (String beanName : beanNames) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            } catch (Throwable ex) {
                ex.printStackTrace();
            }
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }

    void detectHandlerMethods(String handler) {
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType(handler) : handler.getClass());
        if (handlerType != null) {
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        } catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

    private ApplicationContext getApplicationContext() {
        return RequestMappingConfig.applicationContext;
    }


    /**
     * 把 service 注册为 URI 默认以 /CALL-服务实现类-方法名
     * @param handler
     * @param method
     * @param mapping
     */
    void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        MATCH.put(getPrefix()+"-" + method.getDeclaringClass().getSimpleName() + "-" + method.getName(), createHandlerMethod(handler, method));
    }


    protected HandlerMethod createHandlerMethod(Object handler, Method method) {
        HandlerMethod handlerMethod;
        if (handler instanceof String) {
            String beanName = (String) handler;
            handlerMethod = new HandlerMethod(getApplicationContext().getBean(beanName)
                    , method);
        } else {
            handlerMethod = new HandlerMethod(handler, method);
        }
        return handlerMethod;
    }


    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method, handlerType);
        return info;
    }

    protected RequestMappingInfo createRequestMappingInfo(Method method, Class<?> handlerType) {
        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(getPrefix()+"-"+handlerType.getSimpleName() + "-" + method.getName());
        return builder.build();
    }


    private String getPrefix() {
        String preFix =   serviceMagicProperties.getCallPrefix();
        if(!preFix.startsWith("/")){
            preFix = "/"+preFix;
        }
        return preFix;
    }


    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Service.class));
    }


    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        String uri = request.getRequestURI();
        if(!MATCH.containsKey(uri)){
            return null;
        }
        return new HandlerExecutionChain(MATCH.get(uri));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RequestMappingConfig.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        initFunction();
        RequestMappingHandlerAdapter adapter = getApplicationContext().getBean(RequestMappingHandlerAdapter.class);
        HandlerMethodReturnValueHandlerComposite handlerComposite = new HandlerMethodReturnValueHandlerComposite();
        // 先放我自己的 hander
        handlerComposite.addHandler(getApplicationContext().getBean(ServiceMagicResultHander.class));
        handlerComposite.addHandlers(adapter.getReturnValueHandlers());
        adapter.setReturnValueHandlers(handlerComposite.getHandlers());
    }

    @Override
    public int getOrder() {
        return -100000;
    }
}
